查看原文
其他

RAG 高效应用指南 03:Query 理解

行百里者半八十 AI花果山
2024-10-20

构建一个检索增强生成 (Retrieval-Augmented Generation, RAG) 应用的 PoC(概念验证,Proof of Concept)过程相对简单,但要将其推广到生产环境中则会面临多方面的挑战。这主要是因为 RAG 系统涉及多个不同的组件,每个组件都需要精心设计和优化,以确保整体性能达到令人满意的水平。


『RAG 高效应用指南』系列将就如何提高 RAG 系统性能进行深入探讨,提供一系列具体的方法和建议。同时读者也需要记住,提高 RAG 系统性能是一个持续的过程,需要不断地评估、优化和迭代


根据具体应用场景选择合适的优化方法及其组合,是优化 RAG 系统的核心策略。


本文是『RAG 高效应用指南』系列的第 3 篇文章,本文将探讨一些主流的 Query 理解技术。

在这篇文章中,笔者将讨论以下几个问题:

  • • 为什么要进行 query 理解

  • • query 理解有哪些技术(从 RAG 角度)

  • • 各种 query 理解技术的实现(基于 LangChain)

为什么要进行 query 理解

在 RAG 系统中,进行 query 理解是非常关键的一步。query 理解指的是对用户提出的问题进行深入分析,提取出关键信息,从而更准确地从知识库中检索出与用户查询最相关的信息,进而生成高质量的回答。

在 RAG 系统中,对用户 query 进行理解,包括但不限于以下原因:

1、用户表达的模糊性

由于自然语言的复杂性,相同的词汇在不同的上下文中可能有不同的含义,query 理解可以帮助系统识别并纠正这些错误,确保准确地理解用户的真正需求。

比如,用户输入「我想知道子龙是谁?」这里的「子龙」可能指代多种含义,如历史人物赵子龙或者某个昵称。又比如,用户输入 「book」,而 book 有多种含义,可能指一本书,也可能指预订一个座位。

通过 query 理解,系统可以分析上下文,判断用户的意图,从而检索到相关的正确信息。

2、query 和 doc 不在同一个语义空间

用户的 query 通常是非结构化的,可能使用非正式或口语化的语言进行自由表达,而文档则可能采用正式的书面表达。query 理解可以帮助将用户的表述转换为文档中术语,从而提高召回率。

比如,用户输入「手机坏了怎么办」,而文档中可能使用的是「手机维修步骤」这样的表述。又比如,用户可能问「如何让网站更快」,而文档内容可能是「提高网站性能的方法」。

当用户的 query 和文档不在同一个语义空间时,这增加了检索系统的复杂性,因为它需要在不同的表达方式、术语使用、上下文信息等方面建立联系。

3、用户的 query 可能比较复杂

用户的 query 有时可能涉及多个子问题或包含多个步骤,需要将复杂的 query 分解成更易处理的部分,逐一进行处理,以便提供准确和完整的答案。

比如,用户 query:「如何用 Python 分析数据,并生成预测报告?」,而文档内容可能是「使用 Python 分析股票数据的方法包括数据获取、数据清洗、特征提取等步骤」、「生成预测报告的方法包括建立预测模型、进行模型训练和测试、生成报告」。在这个例子,用户的 query 涉及数据分析和报告生成两个主要部分。通过 query 理解,系统可以将复杂的 query 分解为两个子问题:「如何用 Python 分析数据?」和「如何生成预测报告?」,然后分别进行处理和回答。

处理复杂 query 时,RAG 系统需要能够识别并分解用户的查询,将其拆分为更小、更具体的子问题。这样不仅可以提高检索的准确性,还可以使生成的回答更加精确和相关。

query 理解有哪些技术

在 RAG 系统中,query 理解技术是提高信息检索效率和准确性的关键。我把当前常用的 query 理解技术分为三大类:query 改写、query 增强和 query 分解,如图所示。当然,也还有很多其他技术,这里先介绍下面这几种。

上下文信息补全

在多轮对话中,用户的当前输入往往包含隐含的指代关系和省略的信息。例如,用户在对话中提到的「它」可能指代之前对话中提到的某个具体事物。如果缺乏这些上下文信息,系统无法准确理解用户意图,从而导致语义缺失,无法有效召回相关信息。

在这种情况下,我们可以使用上下文信息补全,这里的上下文不仅仅是指多轮对话的信息,还包含当前对话的背景信息,比如时间、地点等。我们可以通过使用大型语言模型(LLM),对当前的 query 进行重写,将上下文中隐含的信息纳入到新生成的 query 中。

下面是一段多轮对话的示例:

User:最近有什么好看的电视剧?
Bot:最近上映了《庆余年 2》,与范闲再探庙堂江湖的故事
User:我想看第一季
Bot:

在这个例子,用户的问题「我想看第一季」包含了隐含的指代信息,没有上下文信息的补全,系统无法知道具体指的是哪部电视剧。通过采用上下文信息补全,我们把前面的对话信息也纳入其中,对 query 进行改写,可以生成类似「我想看庆余年第一季」的完整 query,从而提高后续检索的清晰度和相关性。

上下文信息补全可以提高 query 的清晰度,使系统能够更准确地理解用户意图,不过,因为需要多调用一次 LLM,会增加整体流程的 latency 问题。因此,我们也需要权衡计算复杂度和延迟的问题。

RAG-Fusion

RAG Fusion 旨在提升搜索精度和全面性,它的核心原理是根据用户的原始 query 生成多个不同角度的 query ,以捕捉 query 的不同方面和细微差别。然后通过使用逆向排名融合(Reciprocal Rank Fusion,RRF)技术,将多个 query 的检索结果进行融合,生成一个统一的排名列表,从而增加最相关文档出现在最终 TopK 列表的机会。

RAG Fusion 的整体流程如图所示,工作流程如下

1、多查询生成:直接使用用户输入的 query 进行查询,查询结果可能太窄导致无法产生较为全面的结果。通过使用 LLM 将原始查询扩展成多样化的查询,从而增加搜索的视野和深度。 

2、逆向排名融合(RRF):RRF 是一种简单而有效的技术,用于融合多个检索结果的排名,以提高搜索结果的质量。它通过将多个系统的排名结果进行加权综合,生成一个统一的排名列表,使最相关的文档更有可能出现在结果的顶部。这种方法不需要训练数据,适用于多种信息检索任务,且在多个实验中表现优于其他融合方法。 

3、生成性输出:将重新排名的文档和查询输入到 LLM ,生成结构化、富有洞见的答案或摘要。

拓展阅读

  • • https://towardsdatascience.com/forget-rag-the-future-is-rag-fusion-1147298d8ad1

  • • https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf

  • • https://github.com/Raudaschl/rag-fusion

Multi-Query

跟 RAG Fusion 类似,MultiQuery 是一种通过生成多种视角的查询来检索相关文档的方法。它使用 LLM 从用户输入的查询生成多个不同的查询视角,然后为每个查询检索一组相关文档,并合并这些结果以获得更全面的文档集合。

跟 RAG Fusion 不同的是,MultiQuery 没有使用 RRF 来融合多个搜索结果列表的排名,而是将多个搜索结果放到 context 中。这样做的好处是能够在上下文中保留更多的检索结果,提供更丰富的信息源,同时减少了在排名融合上的复杂性。通过这种方法,用户可以获得更加多样化和全面的信息集合,有助于更好地理解和回答复杂的问题。

拓展阅读

  • • https://python.langchain.com/v0.2/docs/how_to/MultiQueryRetriever/

  • • https://github.com/langchain-ai/rag-from-scratch

HyDE

通常,RAG 向量检索通过使用内积相似度来度量查询(query)和文档(doc)之间的相似性。事实上,这里存在一个挑战:query 和 doc 不在同一个语义空间(前面已经介绍),通过将 query 和 doc 向量化,然后基于向量相似性来检索,检索的精度有限而且噪声可能比较大。为了解决这个问题,一种可行的方法是通过标注大量的数据来训练 embedding 函数。

而 HyDE(假设性文档嵌入,Hypothetical Document Embeddings)技术是一种无监督的方法,它基于这样一个假设:与 query 相比,假设性回答(LLM 直接对 query 生成的答案)与文档共享更相似的语义空间。

HyDE 具体是怎么工作的呢?

首先,HyDE 针对 query 直接生成一个假设性文档或者说回答(hypo_doc)。然后,对这个假设性回答进行向量化处理。最后,使用向量化的假设性回答去检索相似文档。

经过这么一顿操作,以前的 query - doc 检索就变成了 query - hypo_doc - doc 的检索,而此时 hypo_doc 和 doc 可能在语义空间上更接近。因此,HyDE 可以在一定程度上提升文档检索的精准度和相关度。

举个例子,假设用户提问「如何提高睡眠质量?」,HyDE 首先生成一个假设性回答,比如「提高睡眠质量的方法包括保持规律的睡眠时间、避免咖啡因和电子设备等。」,这个假设回答经过编码后,可能与提供的知识库中的文档内容(如不喝咖啡,不玩手机等电子设备)更接近,从而更容易找到相关文档。

HyDE 的核心优势在于:

  1. 1. 避免了在同一向量空间中学习两个嵌入函数的复杂性。

  2. 2. 利用无监督学习,直接生成和利用假设文档。

  3. 3. 在缺乏标注数据的情况下,仍能显著提高检索的准确性和效率。

然而,因为 HyDE 强调问题的假设性回答和查找内容的相似性,因此也存在着不可避免的问题,即,假设性回答的质量取决于大型语言模型的生成能力,如果模型生成的回答不准确或不相关,会影响检索效果。例如,如果讨论的主题对 LLM 来说比较陌生,这种方法就无效了,可能会导致生成错误信息的次数增加。

拓展阅读

  • • https://arxiv.org/pdf/2212.10496

  • • https://docs.llamaindex.ai/en/stable/examples/query_transformations/HyDEQueryTransformDemo/#failure-case-1-hyde-may-mislead-when-query-can-be-mis-interpreted-without-context

  • • https://github.com/langchain-ai/rag-from-scratch

Step-back prompting

Step-back prompting 技术旨在提高 LLM 进行抽象推理的能力,它引导 LLM 在回答问题前进行深度思考和抽象处理,将复杂问题分解为更高层次的问题。

Step-Back Prompting 包含两个主要步骤:

  1. 1. 抽象(Abstraction):不是直接针对问题进行回答,而是首先促使 LLM 提出一个更高级别的「回溯问题」(step-back question),这个问题涉及更广泛的高级概念或原则,并检索与这些概念或原则相关的相关事实。

  2. 2. 推理(Reasoning):在高级概念或原则的基础上,利用语言模型的内在推理能力,对原始问题进行推理解答。这种方法被称为基于抽象的推理(Abstraction-grounded Reasoning)。

研究者在多个挑战性推理密集型任务上测试了 Step-Back Prompting,包括 STEM、知识问答(Knowledge QA)和多跳推理(Multi-Hop Reasoning)。实验涉及 PaLM-2L、GPT-4 和 Llama2-70B 模型,并观察到在各种任务上的性能显著提升。例如,在 MMLU(物理和化学)上,PaLM-2L 的性能分别提高了 7% 和 11%,在 TimeQA 上提高了 27%,在 MuSiQue 上提高了 7%。

Step-Back Prompting 适用于需要复杂推理的领域,如:

  • • STEM 领域:涉及物理和化学等科学原理的应用问题。

  • • 知识问答:需要大量事实性知识的问题回答。

  • • 多跳推理:需要通过多个步骤或信息源进行推理的问题。

拓展阅读

  • • https://arxiv.org/pdf/2310.06117

  • • https://blog.langchain.dev/query-transformations/

  • • https://github.com/langchain-ai/rag-from-scratch

IR-CoT

IR-CoT(Interleaving Retrieval with Chain-of-Thought Reasoning),是一种用于解决多步骤问题(Multi-Step Questions)的技术。IR-CoT 通过交替执行检索(retrieval)和推理(reasoning)步骤来提高大型语言模型(LLMs)在处理复杂问题时的性能,如图所示。

IR-CoT 的核心思想是将检索步骤与推理步骤相结合,以指导检索过程并反过来使用检索结果来改进推理链(Chain-of-Thought, CoT)。论文作者认为,对于多步 QA 任务,单纯基于问题的一次性检索是不够的,因为后续检索的内容取决于已经推导出的信息。

IR-CoT的工作流程如下:

  1. 1. 初始化检索:使用问题作为查询,从知识库中检索一组相关段落。

  2. 2. 交替执行两个步骤

  • • 扩展 CoT:利用问题、到目前为止收集的段落和已经生成的 CoT 句子来生成下一个 CoT 句子。

  • • 扩展检索信息:使用上一个 CoT 句子作为查询来检索额外的段落,并将它们添加到已收集的段落集中。

  • 3. 重复上述步骤:直到 CoT 报告答案或达到最大允许的推理步骤数。

  • 4. 终止:返回所有收集的段落作为检索结果,并使用这些段落作为上下文,通过直接 QA 提示或CoT 提示来回答原始问题。

  • 这里,我们以论文中的一个例子来进行说明:

    如图所示,

    1. 1. 初始问题:Lost Gravity 是在哪个国家制造的?

    2. 2. CoT 生成与检索步骤交替:

    • • 第一步检索:系统首先进行一次检索,获得相关信息,结果发现「Lost Gravity 是由 Mack Rides 制造的」。

    • • 第二步检索:基于之前的结果,系统进行进一步的检索,发现「Mack Rides 是一家来自德国的公司」。

    • • 第三步检索:继续累积文档,确认这一信息。

  • 3. 最终回答:根据累积的信息做出回答:「答案是德国」。

  • IR-CoT 通过交替进行 CoT 生成和检索步骤,使得每一步的检索过程都受到前一步的推理结果指导,从而可以更精确地定位相关信息。

    这相比于传统的单次检索方法,即只使用初始问题作为查询的方式,能够获得更多相关且精确的信息,提高了最终答案的准确性。

    拓展阅读

    • • https://arxiv.org/pdf/2212.10509

    • • https://github.com/langchain-ai/rag-from-scratch

    Least-to-Most

    Least-to-Most prompting 也可以用于解决多步推理的问题,它将复杂问题分解为一系列更简单的子问题,并按顺序解决这些子问题。

    Least-to-Most prompting 包括两个主要阶段:

    1. 1. 分解(Decomposition):在这个阶段,将复杂问题分解为更简单的子问题。

    2. 2. 子问题解决(Subproblem Solving):在这个阶段,使用之前解决的子问题的答案来帮助解决当前子问题。

    这种方法的核心思想是将问题分解为可管理的步骤,并利用逐步构建的答案来指导模型解决更复杂的问题。

    这里,我们以论文中的一个例子来进行说明:

    如图所示,展示了如何通过两阶段的方法使用 LLM 解决一个数学题。

    问题:Amy 爬到滑梯顶部需要4分钟,从滑梯滑下需要1分钟。水滑梯将在15分钟后关闭。她在关闭之前可以滑多少次?

    阶段 1:将问题分解为子问题

    LLM 将复杂问题分解为更小的子问题:为了回答「她在关闭之前可以滑多少次?」,我们需要先解决「每次滑行需要多长时间?」的问题。

    阶段 2:依次解决子问题

    子问题1: 问:每次滑行需要多长时间?答:Amy 爬上滑梯需要4分钟,滑下来需要1分钟。因此每次滑行需要5分钟。

    子问题2: 问:她在关闭之前可以滑多少次?答:水滑梯将在15分钟后关闭。每次滑行需要5分钟。因此 Amy 可以滑15 ÷ 5 = 3次。

    拓展阅读

    • • https://arxiv.org/pdf/2205.10625

    • • https://github.com/langchain-ai/rag-from-scratch

    各种 query 理解技术的实现

    知道了各种 query 理解技术的原理,我们就可以尝试使用 LangChain 来实现了。笔者将所有实现代码放在jupyter notebook 中了,需要的读者,请关注公众号,然后发消息 query 获取 notebook 代码

    在代码中,我使用了 bge-m3 向量模型和 DeepSeek 大模型,它们都可以很方便地获取到。在使用上述 notebook 代码之前,大家需要先安装如下 python 依赖库:

    langchain
    langchain-openai
    langchain_community
    tiktoken
    langchainhub
    chromadb
    beautifulsoup4
    unstructured
    lxml
    sentence-transformers

    另外,读者还需要去 DeepSeek 官网(https://platform.deepseek.com/usage)申请 API Key,刚注册的时候,会送 500 万 token。

    这里,给大家展示下上下文信息补全的实现,更多代码可以参考笔者的 notebook(关注公众号,然后发消息 query 进行获取)。

    上下文信息补全代码如下:

    from langchain_openai import ChatOpenAI
    from langchain_core.prompts import PromptTemplate

    llm = ChatOpenAI(
        model="deepseek-chat"
        temperature=0
        api_key=your_api_key,
        base_url="https://api.deepseek.com",
        streaming=False
    )

    system_prompt = """
    使用聊天对话中的上下文重新表述用户问题,使其成为一个独立完整的问题,不要翻译聊天历史和用户问题。
    <conversation></conversation> 标签中的内容属于聊天对话历史。
    <question></question> 标签中的内容属于用户的问题。
    省略开场白,不要解释,根据聊天对话历史和当前用户问题,生成一个独立完整的问题。
    将独立问题放在 <standalone_question> 标签中。
    """


    user_prompt = """
    <conversation>
    User:最近有什么好看的电视剧?
    Bot:最近上映了《庆余年 2》,与范闲再探庙堂江湖的故事
    </conversation>

    <question>
    User:我想看第一季
    </question>
    """


    messages = [
        {"role""system""content": system_prompt},
        {"role""user""content": user_prompt},
    ]

    response = llm.invoke(messages)

    结果如下:

    总结

    构建一个高效的 RAG 系统,不仅仅是技术上的挑战,更是对我们理解和应对信息复杂性的考验。本文详细阐述了 Query 理解的重要性、原理和技术实现,揭示了在 RAG 系统中,精确的 Query 理解是提升信息检索与生成质量的关键。

    Query 理解作为连接用户需求和知识库的桥梁,其核心在于能够准确捕捉用户的意图,并在海量信息中找到最相关的内容。

    一个性能优良的 RAG 系统,离不开对 Query 理解的持续优化。这一过程不仅是技术的迭代,更是对用户需求的深刻理解。还是那句话:提高 RAG 系统性能是一个持续的过程,需要不断地评估、优化和迭代

    继续滑动看下一个
    AI花果山
    向上滑动看下一个

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存